package org.gap.jseed.contract;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.gap.jseed.Injector;
import org.gap.jseed.contract.annotation.Ensure;
import org.gap.jseed.contract.annotation.Invariant;
import org.gap.jseed.contract.annotation.NotEmpty;
import org.gap.jseed.contract.annotation.NotNull;
import org.gap.jseed.contract.annotation.Require;
import org.gap.jseed.injection.ClassWriter;
import org.gap.jseed.reflect.ClassAnnotationIterator;
import org.gap.jseed.reflect.MethodIterator;

public class ContractServiceInjector implements Injector {
	private Map<Class<? extends Annotation>, Validation> validations = new HashMap<Class<? extends Annotation>, Validation>();
	
	@Override
	public void inject(ClassWriter javaWriter, Class<?> clazz) {
		handlClassAnnotations(javaWriter, clazz);
		
		for (Method each : clazz.getMethods()) {
			handleMethodAnnotations(javaWriter, each);
		}
	}

	private void handlClassAnnotations(ClassWriter javaWriter, Class<?> clazz) {
		for (Annotation each : new ClassAnnotationIterator(clazz)) {
			Class<? extends Annotation> annotationType = each.annotationType();
			if (validations.containsKey(annotationType)) {
				applyToAllPublicMethods(javaWriter, each, clazz);
			}
		}
	}

	private void applyToAllPublicMethods(ClassWriter javaWriter, Annotation annotation, Class<?> clazz) {
		for (Method each : new MethodIterator(clazz)) {
			int modifiers = each.getModifiers();
			if (Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers)) {
				Validation validation = validations.get(annotation.annotationType());
				validation.injectBehavior(javaWriter, each, annotation);
			}
		}
	}

	private void handleMethodAnnotations(ClassWriter javaWriter, Method method) {
		for (Annotation each : method.getAnnotations()) {
			Class<? extends Annotation> annotationType = each.annotationType();
			if (validations.containsKey(annotationType)) {
				validations.get(annotationType).injectBehavior(javaWriter, method, each);
			}
		}
	}

	public void add(Class<? extends Annotation> annotation, Validation contract) {
		validations.put(annotation, contract);
	}

	public static ContractServiceInjector withAllAnnotations() {
		ContractServiceInjector result = new ContractServiceInjector();

		result.add(NotNull.class, new NotNullValidation());
		result.add(NotEmpty.class, new NotEmptyValidation());
		result.add(Require.class, new RequireValidation());
		result.add(Ensure.class, new EnsureValidation());
		result.add(Invariant.class, new InvariantValidation());
		
		return result;
	}
}
